﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Text;
using BlindData.Names;

namespace BlindData
{
	public static class ValueGenerator
	{
		#region int

		/// <summary>
		/// Returns a random int.
		/// </summary>
		/// <returns></returns>
		public static int Value(this INext<int> next)
		{
			Contract.Requires(next != null);

			return next.Between(int.MinValue, int.MaxValue);
		}

		public static int Percentage(this INext<int> next)
		{
			Contract.Requires(next != null);

			return next.Between(0, 100);
		}

		#endregion

		/// <summary>
		/// Returns a random long.
		/// </summary>
		/// <returns></returns>
		public static long Value(this INext<long> next)
		{
			Contract.Requires(next != null);

			return next.Between(long.MinValue, long.MaxValue);
		}

		#region double

		/// <summary>
		/// Returns a random double.
		/// </summary>
		/// <returns></returns>
		public static double Value(this INext<double> next)
		{
			Contract.Requires(next != null);

			return next.Between(double.MinValue, double.MaxValue);
		}

		/// <summary>
		/// Returns a random percentage from 0 to 1.
		/// </summary>
		/// <param name="next"></param>
		/// <returns></returns>
		public static double Percentage(this INext<double> next)
		{
			Contract.Requires(next != null);

			return next.Between(0, 1);
		}

		#endregion

		/// <summary>
		/// Returns a random float.
		/// </summary>
		/// <returns></returns>
		public static float Value(this INext<float> next)
		{
			Contract.Requires(next != null);

			return next.Between(float.MinValue, float.MaxValue);
		}

		/// <summary>
		/// Returns a random decimal.
		/// </summary>
		/// <returns></returns>
		public static decimal Value(this INext<decimal> next)
		{
			Contract.Requires(next != null);

			return next.Between(decimal.MinValue, decimal.MaxValue);
		}

		#region bool

		/// <summary>
		/// Returns a random bool.
		/// </summary>
		/// <returns></returns>
		public static bool Value(this INext<bool> next)
		{
			Contract.Requires(next != null);

			return (next.Generator.Next<int>().LessThanNonNegative(2) == 0);
		}

		/// <summary>
		/// Returns a random bool with the probabiliy of time being true.
		/// </summary>
		/// <param name="next"></param>
		/// <param name="truePercent">The probabiliy that true is returned</param>
		/// <returns></returns>
		public static bool WithTrueProbability(this INext<bool> next, Probability probability)
		{
			Contract.Requires(next != null);

			return (next.Generator.NextProbability() < probability.ToDouble());
		}

		/// <summary>
		/// Returns a random bool with the probabiliy of time being false.
		/// </summary>
		/// <param name="next"></param>
		/// <param name="truePercent">The probabiliy that false is returned</param>
		/// <returns></returns>
		public static bool WithFalseProbability(this INext<bool> next, Probability probability)
		{
			Contract.Requires(next != null);

			return !next.WithTrueProbability(probability);
		}

		#endregion

		#region DateTime

		/// <summary>
		/// Returns a random DateTime.
		/// </summary>
		/// <returns></returns>
		public static DateTime Value(this INext<DateTime> next)
		{
			Contract.Requires(next != null);

			return next.Between(DateTime.MinValue, DateTime.MaxValue);
		}

		public static DateTime WithinNext(this INext<DateTime> next, TimeSpan timeSpan)
		{
			Contract.Requires(next != null);
			
			return next.Between(DateTime.Now, DateTime.Now.Add(timeSpan));
		}

		public static DateTime WithinNextYear(this INext<DateTime> next)
		{
			Contract.Requires(next != null);

			return next.WithinNext(TimeSpan.FromDays(365));
		}

		public static DateTime WithinNext30Days(this INext<DateTime> next)
		{
			Contract.Requires(next != null);

			return next.WithinNext(TimeSpan.FromDays(30));
		}

		public static DateTime WithinNextDay(this INext<DateTime> next)
		{
			Contract.Requires(next != null);

			return next.WithinNext(TimeSpan.FromDays(1));
		}

		public static DateTime WithinNextHour(this INext<DateTime> next)
		{
			Contract.Requires(next != null);

			return next.WithinNext(TimeSpan.FromHours(1));
		}

		public static DateTime WithinPast(this INext<DateTime> next, TimeSpan timeSpan)
		{
			Contract.Requires(next != null);

			return next.Between(DateTime.Now.Subtract(timeSpan), DateTime.Now);
		}

		public static DateTime WithinPastYear(this INext<DateTime> next)
		{
			Contract.Requires(next != null);

			return next.WithinPast(TimeSpan.FromDays(365));
		}

		public static DateTime WithinPast30Days(this INext<DateTime> next)
		{
			Contract.Requires(next != null);

			return next.WithinPast(TimeSpan.FromDays(30));
		}

		public static DateTime WithinPastDay(this INext<DateTime> next)
		{
			Contract.Requires(next != null);

			return next.WithinPast(TimeSpan.FromDays(1));
		}

		public static DateTime WithinPastHour(this INext<DateTime> next)
		{
			Contract.Requires(next != null);

			return next.WithinPast(TimeSpan.FromHours(1));
		}

		#endregion

		#region char

		/// <summary>
		/// Returns a random letter.
		/// </summary>
		/// <returns></returns>
		public static char Letter(this INext<char> next)
		{
			Contract.Requires(next != null);

			var start = next.Generator.Next<bool>().Value() ? 65 : 97;
			return CreateChar(next, start, 26);
		}

		/// <summary>
		/// Returns a random uppercase letter.
		/// </summary>
		/// <returns></returns>
		public static char UppercaseLetter(this INext<char> next)
		{
			Contract.Requires(next != null);

			return CreateChar(next, 65, 26);
		}

		/// <summary>
		/// Returns a random lowercase letter.
		/// </summary>
		/// <returns></returns>
		public static char LowercaseLetter(this INext<char> next)
		{
			Contract.Requires(next != null);

			return CreateChar(next, 97, 26);
		}

		private static char CreateChar(INext<char> next, int start, int range)
		{
			return Convert.ToChar(Convert.ToInt32(Math.Floor(range * next.Generator.NextProbability() + start)));
		}

		#endregion

		#region string

		/// <summary>
		/// Returns a random string of letter.
		/// </summary>
		/// <param name="length">The length of the string.</param>
		/// <returns></returns>
		public static string Value(this INext<string> next, int length)
		{
			Contract.Requires(next != null);

			var sb = new StringBuilder();
			for (int i = 0; i < length; i++)
			{
				sb.Append(next.Generator.Next<char>().Letter());
			}
			return sb.ToString();
		}

		/// <summary>
		/// Returns a random string of letter.
		/// </summary>
		/// <param name="minLength">The min length of the string.</param>
		/// <param name="maxLength">The exclusive max length of the string.</param>
		/// <returns></returns>
		public static string Value(this INext<string> next, int minLength, int maxLength)
		{
			Contract.Requires(next != null);

			var sb = new StringBuilder();
			for (int i = 0; i < next.Generator.Next<int>().Between(minLength, maxLength); i++)
			{
				sb.Append(next.Generator.Next<char>().Letter());
			}
			return sb.ToString();
		}

		/// <summary>
		/// Returns a random string made up of number with the given length
		/// </summary>
		/// <param name="next"></param>
		/// <param name="length">Total number of numbers</param>
		/// <returns></returns>
		public static string Numeric(this INext<string> next, int length)
		{
			Contract.Requires(next != null);
			Contract.Requires(length > 0);

			return next.Numeric(length, length + 1);
		}

		/// <summary>
		/// Returns a random string made up of number with the length in between the min and max
		/// </summary>
		/// <param name="next"></param>
		/// <param name="minLength">Minimum number of characters</param>
		/// <param name="maxLength">Non-inclusive maximum number of characters</param>
		/// <returns></returns>
		public static string Numeric(this INext<string> next, int minLength, int maxLength)
		{
			Contract.Requires(next != null);
			Contract.Requires(minLength < maxLength);

			var sb = new StringBuilder();
			var length = next.Generator.Next<int>().Between(minLength, maxLength);
			for (int i = 0; i < length; i++)
			{
				sb.Append(next.Generator.Next<int>().Between(0, 10));
			}

			return sb.ToString();
		}

		#endregion

		#region IList

		/// <summary>
		/// Returns a random index from the list.
		/// </summary>
		/// <param name="list">The list.</param>
		/// <returns></returns>
		public static int Index<T>(this INext<IList<T>> next, IList<T> list)
		{
			Contract.Requires(next != null);
			Contract.Requires(list != null);
			Contract.Requires(list.Count > 0);

			return next.Generator.Next<int>().LessThanNonNegative(list.Count);
		}

		/// <summary>
		/// Returns a random value from the specified list.
		/// </summary>
		/// <param name="list">The list.</param>
		/// <returns></returns>
		public static T Value<T>(this INext<IList<T>> next, IList<T> list)
		{
			Contract.Requires(next != null);

			return list[next.Index<T>(list)];
		}

		/// <summary>
		/// Returns a random value from the specified list.
		/// </summary>
		/// <param name="list">The list of possible values.</param>
		/// <returns></returns>
		public static T Value<T>(this INext<IList<T>> next, params T[] list)
		{
			Contract.Requires(next != null);

			return list[next.Index<T>(list)];
		}

		#endregion

		public static Name Value(this INext<Name> next, INameGenerator nameGenerator)
		{
			Contract.Requires(next != null);
			Contract.Requires(nameGenerator != null);

			return nameGenerator.NextName();
		}

		//public static string CommaSeperatedValue(this INext<StreamReader> next, FileStream stream)
		//{
		//    if (next == null)
		//    {
		//        throw new ArgumentNullException("next");
		//    }
		//    if (stream == null)
		//    {
		//        throw new ArgumentNullException("stream");
		//    }
		//    if (!stream.CanSeek)
		//    {
		//        throw new InvalidOperationException("FileStream provided can not seek");
		//    }
		//    if (!stream.CanRead)
		//    {
		//        throw new InvalidOperationException("FileStream provided can not be read from");
		//    }
		//    if (stream.Length == 0)
		//    {
		//        throw new InvalidOperationException("FileStream provided has no data");
		//    }

		//    stream.Seek(next.Generator.Next<long>().LessThanNonNegative(stream.Length), SeekOrigin.Begin);
		//}
	}
}
